#include "pl8comm.h"

pl8Comm::pl8Comm(QObject * parent) :
    QObject(parent)
{
    if (ftdi_init(&ftdic) < 0)
        return;

//    ftdi_set_baudrate(&ftdic, 19200);

//    ftdiOpen();

    timer.stop();
    connect(&timer,SIGNAL(timeout()),this,SLOT(timerEvent()));

    resetFlags();
}


pl8Comm::~pl8Comm()
{
//        ftdiClose();
        ftdi_deinit(&ftdic);
}

bool pl8Comm::ftdiOpen()
{
    if(ftdi_usb_open(&ftdic, 0x0403, 0x6001) < 0)
        return false;

    ftdi_set_baudrate(&ftdic, 19200);
    return true;
}

bool pl8Comm::ftdiClose()
{
    return (ftdi_usb_close(&ftdic) >=0 );
}

int pl8Comm::readRam()
{
    int ret;

    if(!ftdiOpen())
        return (PL8C_CHARGER_NOT_READY);

    char wbuf[] = {'R','a','m',0};
    ret = ftdi_write_data(&ftdic, (unsigned char*)&wbuf, sizeof(wbuf));

    if(ret!=sizeof(wbuf))
        return (PL8C_ERROR_WRONG_RETURN_SIZE);

    quint8 rbuf[256] = {0};
    ret = ftdi_read_data(&ftdic, (unsigned char*)&rbuf, 256);

    quint16 checkSumVal = rbuf[ret-1] | (rbuf[ret-2] << 8); // get crc word (MSB first)
    if(calcCRC(&rbuf[5], 147, 2342) != checkSumVal)
        return (PL8C_ERROR_CRC_FAILED);   //crc check

    memcpy(&rpk, &rbuf, sizeof(rpk));

    flipMSB(); // flip all values so they are set correctly;

    ftdiClose();
    return 0;
}

void swap16(quint16 * a)
{
    quint16 b = *a;
    *a = (b>>8) | (b<<8);
}

void swap32(quint32 * a)
{
    quint32 b = *a;
    *a = (b << 24) | (b >> 24) | ((b & 0x0000FF00) << 8)  | ((b & 0x00FF0000) >> 8);
}

void pl8Comm::flipMSB()
{

//    // 32 bit values
    swap32((quint32 *)&rpk.mahInToBattery);
    swap32((quint32 *)&rpk.mahOutOfBattery);

//    // 16 bit value arrays [8]
    for(int i=0; i<8; i++)
    {
        swap16((quint16 *)&rpk.cellVolt[i]);
        swap16((quint16 *)&rpk.cellInternalResistence[i]);
    }

    //16 bit values
    swap16((quint16 *)&rpk.fwVersion);
    swap16((quint16 *)&rpk.syncPWMDrive);
    swap16((quint16 *)&rpk.chargeCurrentSetPoint);
    swap16((quint16 *)&rpk.supplyVoltsWCurrent);
    swap16((quint16 *)&rpk.supplyVolts);
    swap16((quint16 *)&rpk.cpuTemp);
    swap16((quint16 *)&rpk.chargeSec);
    swap16((quint16 *)&rpk.fastApmsReading);
    swap16((quint16 *)&rpk.outputPositiveReading);
    swap16((quint16 *)&rpk.avgFuel);
    swap16((quint16 *)&rpk.startFuel);
    swap16((quint16 *)&rpk.avgAmpsReading);
    swap16((quint16 *)&rpk.statusFlags);
    swap16((quint16 *)&rpk.rxStatusFlags);
    swap16((quint16 *)&rpk.status2Flags);
    swap16((quint16 *)&rpk.vrAmps);
    swap16((quint16 *)&rpk.nicdFallbackVolts);
    swap16((quint16 *)&rpk.maxCellVolts);
    swap16((quint16 *)&rpk.status6Flags);
    swap16((quint16 *)&rpk.chargeMin);
    swap16((quint16 *)&rpk.supplyAmps);
    swap16((quint16 *)&rpk.batteryPositive);
    swap16((quint16 *)&rpk.regenVoltSetPoint);
    swap16((quint16 *)&rpk.dischargeSetAmps);
    swap16((quint16 *)&rpk.internalDischargePWM);
    swap16((quint16 *)&rpk.negativeNodeVoltDrop);
    swap16((quint16 *)&rpk.positiveNodeVoltDrop);
    swap16((quint16 *)&rpk.batteryNegative);
    swap16((quint16 *)&rpk.startSupplyVolts);
    swap16((quint16 *)&rpk.vrOffset);
    swap16((quint16 *)&rpk.slowAvgAmps);
    swap16((quint16 *)&rpk.presetSetChargeAmps);
    swap16((quint16 *)&rpk.crcChecksum);
}

void pl8Comm::getCellVolt(float * vals) // 8 * 16bit values
{
    for(int i=0; i<8; i++)
        vals[i] = ((float)rpk.cellVolt[i]) * 5.12 / 65536;
}

void pl8Comm::getCellInternalResistence(float * vals) // 8 * 16bit values
{
    //( 16bit / 6.3984 – VROffset ) / VRAmps
    for(int i=0; i<8; i++)
    {
        if(getVRAmps() == 0)
            vals[i] = 0;
        else
            vals[i] = (((float)rpk.cellInternalResistence[i]) / 6.3984 - getVROffset()) / getVRAmps() ;
    }
}

void pl8Comm::getBalancerPWM(quint8 * vals)
{
    for(int i=0; i<8; i++)
        vals[i] = rpk.balancerPWM[i];
}

quint32 pl8Comm::getChargeSeconds()
{
    quint32 a;
    if(rpk.chargeMin > (60*18)) // if > 18hrs
    {
        a = rpk.chargeSec - (60*60*18);
        a += rpk.chargeMin * 60;
    }
    else
        a = rpk.chargeSec;

    return a;
}

quint16 pl8Comm::calcCRC(quint8 * gdata, int len, int crcInit)
{
    quint16 rem = crcInit;

    for(int i=0; i<len; i++)
    {
        qint16 data = (*gdata)&0x00FF;

        for(int c=0; c<8; c++)
        {
            if((rem ^ data) & 0x0001)
            {
                rem /= 2;
                rem ^= 0x8408;
            }
            else
                rem /= 2;
            data /= 2;
        }
        gdata++;
    }

    return rem;
}

void pl8Comm::resetFlags()
{
    highTempFired = false;
    chargeDischargeCompleteFired = false;
}

int pl8Comm::selectPreset(quint8 p)
{
    int ret;

    if(!ftdiOpen())
        return (PL8C_CHARGER_NOT_READY);

    char wbuf[] = {'S','e','l','P',p};
    ret = ftdi_write_data(&ftdic, (unsigned char*)&wbuf, sizeof(wbuf));

    if(ret!=sizeof(wbuf))
        return (PL8C_ERROR_WRONG_RETURN_SIZE);

    quint8 rbuf[sizeof(wbuf) + 4] = {0};
    ret = ftdi_read_data(&ftdic, (unsigned char*)&rbuf, sizeof(rbuf));

    quint16 checkSumVal = rbuf[ret-1] | (rbuf[ret-2] << 8); // get crc word (MSB first)
    if(calcCRC(&rbuf[6], 1, 4372) != checkSumVal)
        return (PL8C_ERROR_CRC_FAILED);   //crc check

    ftdiClose();
    return 0;
}

int pl8Comm::chargerOperation(char opr)
{
    resetFlags();
    int ret;

    if(!ftdiOpen())
        return (PL8C_CHARGER_NOT_READY);

    char wbuf[] = {'S','e','l',opr};
    ret = ftdi_write_data(&ftdic, (unsigned char*)&wbuf, sizeof(wbuf));

    if(ret!=sizeof(wbuf))
        return (PL8C_ERROR_WRONG_RETURN_SIZE);

    quint8 rbuf[sizeof(wbuf) + 2] = {0};
    ftdi_read_data(&ftdic, (unsigned char*)&rbuf, sizeof(rbuf));

    if((rbuf[4] != 0x05) || (rbuf[5] != 0xDC))
        return (PL8C_ERROR_CRC_FAILED);

    ftdiClose();
    return 0;
}

int pl8Comm::readPresets()
{
    int ret;

    if(!ftdiOpen())
        return (PL8C_CHARGER_NOT_READY);

    char wbuf[] = {'P','r','s','t'};
    ret = ftdi_write_data(&ftdic, (unsigned char*)&wbuf, sizeof(wbuf));

    if(ret!=sizeof(wbuf))
        return (PL8C_ERROR_WRONG_RETURN_SIZE);

    quint8 rbuf[PRESET_READ_BUF_LEN] = {0};
    ret = ftdi_read_data(&ftdic, (unsigned char*)&rbuf, PRESET_READ_BUF_LEN);

    quint16 checkSumVal = rbuf[ret-1] | (rbuf[ret-2] << 8); // get crc word (MSB first)
//    if(calcCRC(&rbuf[5], 147, 2342) != checkSumVal)
//        return (PL8C_ERROR_CRC_FAILED);   //crc check


    qDebug() << "readPreset bytes read: " << ret;
    qDebug() << "readPreset crc: " << checkSumVal;

//    if(ret==2558)
//    {
//        for(int i=0; i<ret; i++)
//        {
//            qDebug() << QString("Byte [%1] - (%2) - %3").arg(i).arg(rbuf[i]).arg(QChar(rbuf[i]));
//        }
//    }

    for(int i=0; i<PRESET_NUM; i++)
    {
        copyToPreset(&rbuf[6+(i*PRESET_STRUCT_LEN)], i);
        qDebug() << tr("(%1):").arg(i) << "*" << presets[i].name << "*";
    }


    ftdiClose();
    return 0;
}

void pl8Comm::startPolling(int period)
{
    timer.start(period);
}

void pl8Comm::stopPolling()
{
    timer.stop();
}

void pl8Comm::restartPolling()
{
    timer.start();
}

void pl8Comm::timerEvent()
{
    if(readRam())
        return; // if error - do not process further

    //fire signals
    if(isChargeDischargeComplete() && !chargeDischargeCompleteFired)
    {
        chargeDischargeCompleteFired = true;
        emit chargeDischargeComplete();
    }

    if(isHighTemp() && !highTempFired)
    {
        highTempFired = true;
        emit highTemp();
    }

    if(rpk.mode == MODE_SYSTEM_STOP_ERROR_OCCURED)
    {
        emit chargerError(rpk.errorCode);
        //            chargerOperation(OPR_PRESS_ACKNOLEDGE); //send this to clear the error
    }

    if(rpk.mode == MODE_HALT_FOR_SAFETY_SCREEN)
    {
        emit requestConfirmation();
    }

    emit ramDataAvailable();
}


int pl8Comm::do3reads() // tries the operation OPR_TRIES times
{
    int i=OPR_TRIES;
    int ret = 1;
    while(i-- && ret)
        ret = readRam();

    return ret;
}

int pl8Comm::do3operations(char opr) // tries the operation OPR_TRIES times
{
    int i=OPR_TRIES;
    int ret = 1;
    while(i-- && ret)
        ret = chargerOperation(opr);

    return ret;
}


//start charge/discharge/monitor/cycle:
//readRam();
//verify rpk.mode == 0
//chargerOperation(OPR_START_####);
bool pl8Comm::chargerStart(char opr)
{
    if(do3reads()) // if read 3 times and still get error -> fail
        return false;

    if(rpk.mode != MODE_CHARGER_READY_TO_START)
        return false;

    return (do3operations(opr) == 0);
}


//stop charge/discharge/monitor/cycle:
//readRam();
//verify rpk.mode == 6, 7, 8, 9 , 11
//chargerOperation(OPR_PRESS_ACKNOLEDGE);
bool pl8Comm::chargerStop()
{
    if(do3reads()) // if read 3 times and still get error -> fail
        return false;


    if(     (rpk.mode != MODE_CHARGING) &&
            (rpk.mode != MODE_TRICKLE_CHARGING) &&
            (rpk.mode != MODE_DISCHARGING) &&
            (rpk.mode != MODE_MONITORING) &&
            (rpk.mode != MODE_PACK_COOL_DOWN))
        return false;

    return (do3operations(OPR_PRESS_ACKNOLEDGE) == 0);
}

//clear error:
//readRam();
//verify rpk.mode == 0
//chargerOperation(OPR_PRESS_ACKNOLEDGE);
bool pl8Comm::chargerClearError()
{
    if(do3reads()) // if read 3 times and still get error -> fail
        return false;

    if(rpk.mode != MODE_SYSTEM_STOP_ERROR_OCCURED)
        return false;

    return (do3operations(OPR_PRESS_ACKNOLEDGE) == 0);
}

void pl8Comm::copyToPreset(quint8 *buf, int presetNum)
{
    for(int i=0; i<PRESET_NAME_LEN; i++)
    {
        presets[presetNum].name[i] = buf[i + PRESET_NAME_OFS + ((i & 1) ? -1 : 1)];
    }
}


QString pl8Comm::getModeName()
{
    if(rpk.mode == MODE_DETECTING_PACK)
        return tr("Detecting Pack");
    if(rpk.mode == MODE_HALT_FOR_SAFETY_SCREEN)
        return tr("Halt for safety screen");
    if(rpk.mode == MODE_TRICKLE_CHARGING)
        return tr("Trickle Charging");
    if(rpk.mode == MODE_PACK_COOL_DOWN)
        return tr("Pack Cool Down");
    if(rpk.mode == MODE_CHARGING)
        return tr("Charging");
    if(rpk.mode == MODE_SYSTEM_STOP_ERROR_OCCURED)
        return tr("SYSTEM STOP - ERROR %1").arg(rpk.errorCode);
    if(rpk.mode == MODE_DISCHARGING)
        return tr("Discharging");
    if(rpk.mode == MODE_MONITORING)
        return tr("Monitoring");


    return tr("Charger Ready"); //MODE_CHARGER_READY_TO_START
}

QString pl8Comm::getChemistryName()
{
    if(rpk.chemistry == CHEMISTRY_LITHIUM_ION)
        return tr("Lithium Ion");
    if(rpk.chemistry == CHEMISTRY_A123)
        return tr("A123");
    if(rpk.chemistry == CHEMISTRY_LITHIUM_MANGANESE)
        return tr("Lithium Manganese");
    if(rpk.chemistry == CHEMISTRY_LEAD_ACID)
        return tr("Lead Acid");
    if(rpk.chemistry == CHEMISTRY_LITHIUM_POLYMER)
        return tr("Lithium Polymer");
    if(rpk.chemistry == CHEMISTRY_LIFE)
        return tr("LiFE");
    if(rpk.chemistry == CHEMISTRY_NICD)
        return tr("NiCD");
    if(rpk.chemistry == CHEMISTRY_POWER_SUPPLY)
        return tr("Power Supply");
    if(rpk.chemistry == CHEMISTRY_NIMH)
        return tr("NiMH");
    if(rpk.chemistry == CHEMISTRY_PRIMARY)
        return tr("Primary");
    if(rpk.chemistry == CHEMISTRY_LITHIUM_COBALT)
        return tr("Lithium Cobalt");

    return tr("Unknown");
}

QString pl8Comm::getPresetName(int presetNum)
{
    return QString(presets[presetNum].name);
}

QString pl8Comm::getPowerReducedReasonName()
{
    if(rpk.powerReducedReason == PRR_SUPPLY_LOW_FOR_HIGH_POWER)
        return tr("Power Supply Low Power");
    if(rpk.powerReducedReason == PRR_LOW_INPUT_VOLTAGE)
        return tr("Low Input Voltage");
    if(rpk.powerReducedReason == PRR_CONSTANT_VOLTAGE_OUTPUT)
        return tr("Constant Voltage Output");
    if(rpk.powerReducedReason == PRR_HIGH_TEMP)
        return tr("High Temp");
    if(rpk.powerReducedReason == PRR_INPUT_CURRENT_LIMIT)
        return tr("Input Current Limit");
    if(rpk.powerReducedReason == PRR_REDUCE)
        return tr("Reduce");
    if((rpk.powerReducedReason == PRR_HIGH_TEMP_DISCHARGE2) || rpk.powerReducedReason == PRR_HIGH_TEMP_DISCHARGE)
        return tr("High Temp Discharge");
    if(rpk.powerReducedReason == PRR_DISCHARGE_REDUCED)
        return tr("Discharge Reduced");
    if(rpk.powerReducedReason == PRR_FULL_POWER_ALLOWED)
        return tr("Full Power Allowed");
    if((rpk.powerReducedReason == PRR_CELL_SUM_ERROR) || (rpk.powerReducedReason == PRR_CELL_SUM_ERROR2))
        return tr("Cell Sum Error");
    if(rpk.powerReducedReason == PRR_REGEN_MAX_AMPS_REACHED)
        return tr("Regen Max Amps Reached");
    if(rpk.powerReducedReason == PRR_REGEN_VOLT_LIMIT_REACHED)
        return tr("Regen Max Volt Reached");
    if(rpk.powerReducedReason == PRR_INTERNAL_MAX_100W_DISCHARGE)
        return tr("Internal Max 100W Discharge");
    if(rpk.powerReducedReason == PRR_SUPPLY_NOISE)
        return tr("Supply Noise");
    if(rpk.powerReducedReason == PRR_60A_LIMIT_INPUT_CURRENT_REACHED)
        return tr("60A Current Limit Reached");

    return tr("Power Not Reduced");
}
